#include "common.h"
#include "client.h"
#include "Encryptor.h"

#define KEYFILE 					"realkeys/customer.pem"
#define CARRIER_TRANSFERED_PUB_KEY	"./temp/tempCarrier.pem"
#define CREDITCO_TRANSFERED_PUB_KEY "./temp/tempCreditCo.pem"
#define PASSWORD					"password"

static int handle_transaction(SSLClient *ssl);
void prep_prelim_order(struct ProductEntry* pe, const int len);
void prep_pi(struct PaymentInfo* pi, struct OrderInfo* oi);
void prep_si(struct ShippingInfo* si, struct OrderInfo* oi);

Encryptor* carrierEncryptor;
Encryptor* creditcoEncryptor;

int main(int argc, char** argv) {
	int mu = 250000;				// average interval value
	int n = 1;

	if (!(argc == 1 || argc == 3 || argc == 5)) {
		printf("Usage: customer [-m USEC] [-n NUM_RUNS]\n");
		exit(0);
	} else if (argc == 3 || argc == 5) {
		std::string arg1(argv[1]);
		if (arg1 == "-m") {
			mu = atoi(argv[2]);
		} else if (arg1 == "-n") {
			n = atoi(argv[2]);
		} else {
			printf("Usage: customer [-m USEC] [-n NUM_RUNS]\n");
		        exit(0);
		}
		if (argc == 5) {
			std::string arg2(argv[3]);
			if (arg2 == "-m") {
				mu = atoi(argv[4]);
			} else if (arg2 == "-n") {
				n = atoi(argv[4]);
			} else {
				printf("Usage: customer [-m USEC] [-n NUM_RUNS]\n");
		                exit(0);
			}
		}
	}

	printf("usec=%d\n", mu);
	printf("runs=%d\n", n);
	fflush(stdout);
	sleep(2);

	//add in CHLD signal handler
	struct sigaction sig_act;

	memset(&sig_act, 0, sizeof(sig_act));
	sig_act.sa_handler = SIG_IGN;

	if (sigaction(SIGCHLD, &sig_act, 0)) {
		perror("sigaction");
		exit(1);
	}

	const char* host = MERCHANT_ADDR;
	int port = MERCHANT_SERVER_PORT;

	pid_t process_id = getpid();
	const gsl_rng_type * T;
	gsl_rng *rng;

	gsl_rng_env_setup();

	T = gsl_rng_default;
	rng = gsl_rng_alloc(T);

    unsigned int intvl = 0, i = 0;
    unsigned long intvl_sum = 0;

	while (process_id != 0 && (n == 0 || i < n)) {
		// parent process; continue to fork ----
		intvl = gsl_ran_poisson(rng, mu);
		if ((process_id = fork()) != -1) {
			printf("[i,intrvl]=[%d,%d]\n", i, intvl);
			i++;
		}
		intvl_sum += intvl;
		usleep(intvl);
	}

	if (process_id == 0) {
		SSLClient* ssl = new SSLClient(KEYFILE, PASSWORD);
		ssl->Connect(host, port);
		handle_transaction(ssl);
		delete (ssl);
	} else {
		pid_t child_pid;
		while ((child_pid = wait(NULL)) != -1) {
			continue;
		}

                system ("rm ./temp/*");
	}
	exit(0);
}

static int handle_transaction(SSLClient *ssl) {
	char buf[BUFSIZZ];
	char carrier_key_buf[64];
	char creditco_key_buf[64];
	unsigned char* encrypt_buf;
	int encrypt_size;
	uint32_t net_encrypt_size;
	int r;
	char in_char;
	int len;
	unsigned char numProducts;

	const char* carrier_key = carrier_key_buf;//CARRIER_TRANSFERED_PUB_KEY;
	const char* creditco_key = creditco_key_buf;//CREDITCO_TRANSFERED_PUB_KEY;

	const char* carrierCommonName = CARRIER_ENC_CN;
	const char* creditcoCommonName = CREDITCO_ENC_CN;


#ifndef NOCERT
  sprintf(carrier_key_buf, "%s%d", CARRIER_TRANSFERED_PUB_KEY, getpid());
  sprintf(creditco_key_buf, "%s%d", CREDITCO_TRANSFERED_PUB_KEY, getpid());

	// Get certificates for the carrier and creditco from the merchant
	FILE* ccPem = fopen(creditco_key, "w");
	while (1) {
		len = ssl->Read(buf, 1);
		fwrite(buf, len, 1, ccPem);

		if (buf[0] == '\0')
			break;
	}

	printf("Got creditco pem\n");

	FILE* carrierPem = fopen(carrier_key, "w");
	while (1) {
		len = ssl->Read(buf, 1);
		fwrite(buf, len, 1, carrierPem);

		if (buf[0] == '\0')
			break;
	}

	printf("Got carrier pem\n");
	fclose(ccPem);
  fclose(carrierPem);
#else
	carrier_key = CARRIER_PUB_KEY;
	creditco_key = CREDITCO_PUB_KEY;
#endif

	// create encryptors
	carrierEncryptor = new Encryptor(&carrier_key, &carrierCommonName, 1);
	creditcoEncryptor = new Encryptor(&creditco_key, &creditcoCommonName, 1);

	// setup OI including SI and PI
	struct OrderInfo oi;
	struct ProductEntry pe[4];
	struct PaymentInfo pi;
	struct ShippingInfo si;

	memset(&si, 0, sizeof(oi));
	memset(&si, 0, sizeof(pe));
	memset(&pi, 0, sizeof(pi));
	memset(&si, 0, sizeof(si));

	// Send Message 1: Preliminary Order Information ----
	oi.totalItemsOrdered = 4;
	prep_prelim_order(pe, 4);
	ssl->Write(&oi, sizeof(struct OrderInfo));
	ssl->Write(pe, sizeof(struct ProductEntry) * oi.totalItemsOrdered);

	// Receive Message 2: Order Confirmation ----
	memset(&oi, 0, sizeof(OrderInfo));
	ssl->ReadUntilFull(&oi, sizeof(struct OrderInfo));
	ssl->ReadUntilFull(pe, sizeof(struct ProductEntry) * oi.totalItemsOrdered);

	// Send Message 3: OI Message ----
	strncpy(oi.shippingCarrierCode, CARRIER_CODE,
			sizeof(oi.shippingCarrierCode));
	strncpy(oi.creditCardCoCode, CREDITCO_CODE, sizeof(oi.creditCardCoCode));
	ssl->Write(&oi, sizeof(struct OrderInfo));

	// Build PI Message ----
	prep_pi(&pi, &oi);

  // Encrypt PI ----
#ifndef NOENCRYPT
  // Build PI Message Digest ----
	unsigned int pi_digest_len = 0;
	unsigned char* pi_str = prep_for_digest(pi);
	unsigned char* pi_digest = sha1_digest(pi_str, strlen((char*) pi_str) + 1,
			&pi_digest_len);
	memcpy(pi.message_digest, pi_digest, pi_digest_len);
	delete[] pi_str;
	delete[] pi_digest;

	encrypt_buf = creditcoEncryptor->MakeEncryptedPacket((unsigned char*) &pi,
			sizeof(struct PaymentInfo), &encrypt_size);
	net_encrypt_size = encrypt_size;
	net_encrypt_size = htonl(net_encrypt_size);
	ssl->Write(&net_encrypt_size, sizeof(net_encrypt_size));
	ssl->Write(encrypt_buf, encrypt_size);
	delete[] encrypt_buf;
#else
	net_encrypt_size = sizeof(struct PaymentInfo);
	net_encrypt_size = htonl(net_encrypt_size);
	ssl->Write(&net_encrypt_size, sizeof(net_encrypt_size));
	ssl->Write(&pi, sizeof(struct PaymentInfo));
#endif

	// Build SI Message ----
	prep_si(&si, &oi);

#ifndef NOENCRYPT
  // Build SI Message Digest ----
	unsigned int si_digest_len = 0;
	unsigned char* si_str = prep_for_digest(si);
	unsigned char* si_digest = sha1_digest(si_str, strlen((char*) si_str) + 1,
			&si_digest_len);
	memcpy(si.message_digest, si_digest, si_digest_len);
	delete[] si_str;
	delete[] si_digest;


	// Encrypt SI ----
	encrypt_buf = carrierEncryptor->MakeEncryptedPacket((unsigned char*) &si,
			sizeof(struct ShippingInfo), &encrypt_size);
	net_encrypt_size = encrypt_size;
	net_encrypt_size = htonl(net_encrypt_size);
	ssl->Write(&net_encrypt_size, sizeof(net_encrypt_size));
	ssl->Write(encrypt_buf, encrypt_size);
	delete[] encrypt_buf;
#else
	net_encrypt_size = sizeof(struct PaymentInfo);
	net_encrypt_size = htonl(net_encrypt_size);
	ssl->Write(&net_encrypt_size, sizeof(net_encrypt_size));
	ssl->Write(&si, sizeof(struct ShippingInfo));
#endif

	// Message 4 Sent ----

	/* Now read the server's response, assuming
	 that it's terminated by a close */
	while (len = ssl->Read(buf, BUFSIZZ - 1)) {
		buf[len] = '\0';
		printf("\n%s\n", buf);
	}

}

// Creates product entry stubs ----
void prep_prelim_order(struct ProductEntry* pe, const int len) {
	unsigned char head = 0xFF;
	unsigned char term = 0xAA;

	switch (len) {
	case 4:
		pe[3].startByte = head;
		pe[3].id[0] = 0xA;
		pe[3].id[1] = 0x8;
		strncpy(pe[3].name, "Design Patterns", sizeof(pe[3].name) - 1);
		strncpy(pe[3].unitPrice, "59.99", sizeof(pe[3].unitPrice) - 1);
		strncpy(pe[3].quantity, "1", sizeof(pe[3].quantity));
		pe[3].endByte = term;
	case 3:
		pe[2].startByte = head;
		pe[2].id[0] = 0x1;
		pe[2].id[1] = 0xC;
		strncpy(pe[2].name, "Effective C++", sizeof(pe[2].name) - 1);
		strncpy(pe[2].unitPrice, "49.99", sizeof(pe[2].unitPrice) - 1);
		strncpy(pe[2].quantity, "1", sizeof(pe[2].quantity));
		pe[2].endByte = term;
	case 2:
		pe[1].startByte = head;
		pe[1].id[0] = 0x2;
		pe[1].id[1] = 0xF;
		strncpy(pe[1].name, "The C++ STL", sizeof(pe[1].name) - 1);
		strncpy(pe[1].unitPrice, "79.99", sizeof(pe[1].unitPrice) - 1);
		strncpy(pe[1].quantity, "1", sizeof(pe[1].quantity));
		pe[1].endByte = term;
	case 1:
		pe[0].startByte = head;
		pe[0].id[0] = 0xE;
		pe[0].id[1] = 0x6;
		strncpy(pe[0].name, "Intro to Algorithms", sizeof(pe[0].name) - 1);
		strncpy(pe[0].unitPrice, "87.00", sizeof(pe[0].unitPrice) - 1);
		strncpy(pe[0].quantity, "1", sizeof(pe[0].quantity));
		pe[0].endByte = term;
		break;
	default:
		printf("Warning: 0 product entries\n");
		break;
	}

//	for (int i = 0; i < len; i++) {
//		printf("startByte: %d\n", pe[i].startByte);
//		printf("id:        %hd\n", (unsigned short) *(pe[i].id));
//		printf("name:      %s\n", pe[i].name);
//		printf("unitPrice: %s\n", pe[i].unitPrice);
//		printf("quantity:  %s\n", pe[i].quantity);
//		printf("endByte:   %d\n\n", pe[i].endByte);
//	}
}

void prep_pi(struct PaymentInfo* pi, struct OrderInfo* oi) {
	int offset = 1;
	strncpy(pi->ch_last_name, "fujinoki", PI_CH_LAST_NAME_LEN);
	strncpy(pi->ch_mid_name, "N/A", PI_CH_MIDDLE_NAME_LEN);
	strncpy(pi->ch_first_name, "h", PI_CH_FIRST_NAME_LEN);
	strncpy(pi->card_num, "1234567812345678", PI_CARD_NUM_LEN);
	strncpy(pi->payment_amount, oi->totalCharged, PI_PAYMENT_AMT_LEN);
	strncpy(pi->payment_date, oi->date, PI_PAYMENT_DATE_LEN);
	strncpy(pi->exp_date, "0911", PI_EXP_DATE_LEN);
	strncpy(pi->order_token, oi->merchant_id, OI_MERCHANT_ID_LEN);
	offset += OI_MERCHANT_ID_LEN;
	strncpy(pi->order_token + offset, oi->date, OI_DATE_LEN);
	offset += OI_DATE_LEN;
	strncpy(pi->order_token + offset, oi->trans_id, OI_TRANS_ID_LEN);
}

void prep_si(struct ShippingInfo* si, struct OrderInfo* oi) {
	int offset = 1;
	strncpy(si->attn_first_name, "Albert", SI_ATTN_FIRST_NAME_LEN);
	strncpy(si->attn_mid_name, "N/A", SI_ATTN_MIDDLE_NAME_LEN);
	strncpy(si->attn_last_name, "Einstein", SI_ATTN_LAST_NAME_LEN);
	strncpy(si->shipping_addr, "1600 Pennsylvania Ave. NW, Washington DC, 20500", SI_SHIPPING_ADDR_LEN);
	strncpy(si->contact_num, "(555) 555-5555", SI_CONTACT_NUM_LEN);
	strncpy(si->handling_req, "HDLG", SI_HANDLING_REQ_LEN);
	strncpy(si->carrier_code, oi->shippingCarrierCode, SI_CARRIER_CODE_LEN);
	strncpy(si->pub_key, "TODO", SI_PUB_KEY_LEN);
	strncpy(si->order_token, oi->merchant_id, OI_MERCHANT_ID_LEN);
	offset += OI_MERCHANT_ID_LEN;
	strncpy(si->order_token + offset, oi->date, OI_DATE_LEN);
	offset += OI_DATE_LEN;
	strncpy(si->order_token + offset, oi->trans_id, OI_TRANS_ID_LEN);
}
